package com.enba.boot.payment.wechatpay;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.enba.boot.payment.base.PayProductBase;
import com.enba.boot.payment.base.RefundProductBase;
import com.enba.boot.payment.properties.WechatPayProperties;
import com.wechat.pay.java.core.Config;
import com.wechat.pay.java.core.RSAAutoCertificateConfig;
import com.wechat.pay.java.core.notification.NotificationConfig;
import com.wechat.pay.java.core.notification.NotificationParser;
import com.wechat.pay.java.core.notification.RequestParam;
import com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction.TradeStateEnum;
import com.wechat.pay.java.service.payments.model.Transaction;
import com.wechat.pay.java.service.payments.nativepay.NativePayService;
import com.wechat.pay.java.service.payments.nativepay.model.Amount;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayRequest;
import com.wechat.pay.java.service.payments.nativepay.model.PrepayResponse;
import com.wechat.pay.java.service.payments.nativepay.model.QueryOrderByOutTradeNoRequest;
import com.wechat.pay.java.service.refund.RefundService;
import com.wechat.pay.java.service.refund.model.AmountReq;
import com.wechat.pay.java.service.refund.model.CreateRequest;
import com.wechat.pay.java.service.refund.model.QueryByOutRefundNoRequest;
import com.wechat.pay.java.service.refund.model.Refund;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;

/** 微信支付Helper */
@Slf4j
public class WechatPayHelper {

  private static final Logger logger = LoggerFactory.getLogger(WechatPayHelper.class);

  private final WechatPayProperties properties;
  private final NativePayService nativePayService;
  private final RefundService refundService;
  private final NotificationConfig wechatConfig;

  public WechatPayHelper(WechatPayProperties properties, NotificationConfig wechatConfig) {
    this.properties = properties;

    Config config =
        new RSAAutoCertificateConfig.Builder()
            .merchantId(properties.getMerchantId())
            // 使用 com.wechat.pay.java.core.util 中的函数从本地文件中加载商户私钥，商户私钥会用来生成请求的签名
            .privateKeyFromPath(properties.getPrivateKeyPath())
            .merchantSerialNumber(properties.getMerchantSerialNumber())
            .apiV3Key(properties.getApiV3Key())
            .build();
    // 下单
    this.nativePayService = new NativePayService.Builder().config(config).build();
    // 退单
    this.refundService = new RefundService.Builder().config(config).build();
    this.wechatConfig = wechatConfig;
  }

  /** 检查支付宝配置信息 */
  public void checkWechatPayProperties() {
    logger.info(
        "微信支付配置信息：{}", JSON.toJSONString(properties, SerializerFeature.WRITE_MAP_NULL_FEATURES));

    if (!StringUtils.hasText(properties.getAppId())
        || !StringUtils.hasText(properties.getMerchantId())
        || !StringUtils.hasText(properties.getPrivateKeyPath())
        || !StringUtils.hasText(properties.getMerchantSerialNumber())
        || !StringUtils.hasText(properties.getApiV3Key())) {
      throw new RuntimeException("请检查微信支付配置信息");
    }
  }

  /**
   * 扫码支付
   *
   * @param productBase 商品信息
   * @return PrepayResponse
   */
  public PrepayResponse payNative(PayProductBase productBase) {
    PrepayRequest request = new PrepayRequest();
    // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义

    // 【公众号ID】 公众号ID
    request.setAppid(properties.getAppId());
    // 【商户号】 商户号
    request.setMchid(properties.getMerchantId());
    // 【商品描述】 商品描述
    request.setDescription(productBase.getSubject());
    // 【商户订单号】 商户系统内部订单号，只能是数字、大小写字母_-*且在同一个商户号下唯一
    request.setOutTradeNo(productBase.getOutTradeNo());
    // 【通知地址】 异步接收微信支付结果通知的回调地址，通知URL必须为外网可访问的URL，不能携带参数。 公网域名必须为HTTPS
    request.setNotifyUrl(productBase.getNotifyUrl());
    // 订单金额信息
    Amount amount = new Amount();
    // 订单总金额，单位为分
    amount.setTotal(Integer.valueOf(productBase.getTotalAmount()));
    request.setAmount(amount);

    return nativePayService.prepay(request);
  }

  /**
   * 查询订单
   *
   * @param outTradeNo 商户订单号
   * @return Transaction
   */
  public Transaction queryOrderByOutTradeNo(String outTradeNo) {
    QueryOrderByOutTradeNoRequest request = new QueryOrderByOutTradeNoRequest();
    // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义

    request.setOutTradeNo(outTradeNo);
    request.setMchid(properties.getMerchantId());

    // 调用接口
    return nativePayService.queryOrderByOutTradeNo(request);
  }

  /**
   * 申请退款
   *
   * @param refundProductBase 退款信息
   * @return Refund refund
   */
  public Refund refund(RefundProductBase refundProductBase) {
    CreateRequest request = new CreateRequest();
    // 商户订单号
    request.setOutTradeNo(refundProductBase.getOutTradeNo());
    // 商户退款单号
    request.setOutRefundNo(refundProductBase.getOutRefundNo());
    // 退款原因
    request.setReason(refundProductBase.getRefundReason());
    // 【退款结果回调url】 异步接收微信支付退款结果通知的回调地址，通知url必须为外网可访问的url，不能携带参数。
    // 如果参数中传了notify_url，则商户平台上配置的回调地址将不会生效，优先回调当前传的这个地址。
    request.setNotifyUrl(refundProductBase.getNotifyUrl());

    AmountReq amountReq = new AmountReq();
    amountReq.setRefund(Long.valueOf(refundProductBase.getRefundAmount()));
    amountReq.setTotal(Long.valueOf(refundProductBase.getRefundAmount()));
    amountReq.setCurrency("CNY");
    request.setAmount(amountReq);

    // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
    // 调用接口
    return refundService.create(request);
  }

  /**
   * 查询退款
   *
   * @param outRefundNo 商户退款单号
   * @return Refund refund
   */
  public Refund queryByOutRefundNo(String outRefundNo) {
    QueryByOutRefundNoRequest request = new QueryByOutRefundNoRequest();
    request.setOutRefundNo(outRefundNo);

    // 调用request.setXxx(val)设置所需参数，具体参数可见Request定义
    // 调用接口
    return refundService.queryByOutRefundNo(request);
  }

  /**
   * 微信支付回调
   *
   * @param request 请求
   * @param func 自定义回调逻辑
   * @param <R> 回调函数返回值类型
   * @return R
   * @throws IOException io异常
   */
  public <R> R notifyCallback(
      HttpServletRequest request,
      Function<com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction, R> func)
      throws IOException {
    InputStream inputStream = request.getInputStream();
    // BufferedReader是包装设计模式，BufferedReader带缓冲而且可以一行一行的读，性能更高
    // stream和reader之间的转换需要一个转换流，InputStreamReader
    BufferedReader in =
        new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
    StringBuilder sb = new StringBuilder();
    String line;
    while ((line = in.readLine()) != null) {
      sb.append(line);
    }
    in.close();
    inputStream.close();

    // 构造 RequestParam
    RequestParam requestParam =
        new RequestParam.Builder()
            .serialNumber(request.getHeader("Wechatpay-Serial"))
            .nonce(request.getHeader("Wechatpay-Nonce"))
            .signature(request.getHeader("Wechatpay-Signature"))
            .timestamp(request.getHeader("Wechatpay-Timestamp"))
            .body(sb.toString())
            .build();

    // 初始化 NotificationParser
    NotificationParser parser = new NotificationParser(wechatConfig);

    // 以支付通知回调为例，验签、解密并转换成 Transaction
    com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction transaction =
        parser.parse(
            requestParam,
            com.wechat.pay.java.service.partnerpayments.nativepay.model.Transaction.class);

    // 判断支付是否成功，处理自己的业务逻辑
    if (TradeStateEnum.SUCCESS.equals(transaction.getTradeState())) {
      log.info("pay success");
    } else {
      log.error("pay failed");
    }

    // 执行回调，自定义成功或失败的业务逻辑
    return func.apply(transaction);
  }
}
